#include <cpr/cpr.h>

#include "common/elements/element_maker.h"
#include "common/elements/item_type.h"

#include "common/log/log.h"

#include "common/more/json.h"
#include "common/more/uuid.h"

#include "client.h"

namespace Api
{

Client::Client(const std::string& datenaroPath)
    : m_datenaroPath(datenaroPath)
{
}

std::vector<unsigned> Client::getIds(
    const Uuids::Uuid& sessionUuid,
    Elements::ElementType type,
    const std::string& filter
) const
{
    // Подготовка тела запроса
    Common::Json requestJson;
    requestJson["sessionUuid"] = Uuids::uuidToString(sessionUuid);
    requestJson["elementType"] = unsigned(type);
    if (!filter.empty())
        requestJson["filter"] = filter;

    // Формирование запроса
    const cpr::Response response = cpr::Post(
        cpr::Url { url("/api/v1/get_ids") },
        cpr::Body { requestJson.dump() },
        cpr::Header { { "Content-Type", "application/json" } }
    );

    // Разбор ответа
    const auto responseJson = Common::stringToJson(response.text);
    return responseJson["ids"].get<std::vector<unsigned>>();
}

std::vector<std::shared_ptr<Elements::Element>> Client::getElements(
    const Uuids::Uuid& sessionUuid,
    Elements::ElementType type,
    const std::vector<unsigned>& ids
) const
{
    // Подготовка тела запроса
    Common::Json requestJson;
    requestJson["sessionUuid"] = Uuids::uuidToString(sessionUuid);
    requestJson["elementType"] = unsigned(type);
    requestJson["ids"] = Common::Json(ids);

    // Формирование запроса
    const cpr::Response response = cpr::Post(
        cpr::Url { url("/api/v1/get_elements") },
        cpr::Body { requestJson.dump() },
        cpr::Header { { "Content-Type", "application/json" } }
    );

    // Разбор ответа
    std::vector<std::shared_ptr<Elements::Element>> result;
    const auto responseJson = Common::stringToJson(response.text);
    for (const auto& element : responseJson["elements"])
        result.emplace_back(Elements::fromJson(element));
    return result;
}

Api::Result Client::addElement(
    const Uuids::Uuid& sessionUuid,
    std::shared_ptr<Elements::Element> element
) const
{
    // Подготовка тела запроса
    Common::Json requestJson;
    requestJson["sessionUuid"] = Uuids::uuidToString(sessionUuid);
    requestJson["element"] = element->toJson();

    // Формирование запроса
    const cpr::Response response = cpr::Post(
        cpr::Url { url("/api/v1/add_element") },
        cpr::Body { requestJson.dump() },
        cpr::Header { { "Content-Type", "application/json" } }
    );

    // Разбор ответа
    const auto responseJson = Common::stringToJson(response.text);
    Result result;
    result.m_isSuccess = responseJson["success"].get<bool>();
    result.m_id = responseJson["id"].get<unsigned>();
    return result;
}

Api::Result Client::setElement(
    const Uuids::Uuid& sessionUuid,
    std::shared_ptr<Elements::Element> element
) const
{
    // Подготовка тела запроса
    Common::Json requestJson;
    requestJson["sessionUuid"] = Uuids::uuidToString(sessionUuid);
    requestJson["element"] = element->toJson();

    // Формирование запроса
    const cpr::Response response = cpr::Post(
        cpr::Url { url("/api/v1/set_element") },
        cpr::Body { requestJson.dump() },
        cpr::Header { { "Content-Type", "application/json" } }
    );

    // Разбор ответа
    const auto responseJson = Common::stringToJson(response.text);
    Result result;
    result.m_isSuccess = responseJson["success"].get<bool>();
    return result;
}

Api::Result Client::delElement(
    const Uuids::Uuid& sessionUuid,
    Elements::ElementType type,
    unsigned id
) const
{
    // Подготовка тела запроса
    Common::Json requestJson;
    requestJson["sessionUuid"] = Uuids::uuidToString(sessionUuid);
    requestJson["elementType"] = unsigned(type);
    requestJson["id"] = id;

    // Формирование запроса
    const cpr::Response response = cpr::Post(
        cpr::Url { url("/api/v1/del_element") },
        cpr::Body { requestJson.dump() },
        cpr::Header { { "Content-Type", "application/json" } }
    );

    // Разбор ответа
    const auto responseJson = Common::stringToJson(response.text);
    Result result;
    result.m_isSuccess = responseJson["success"].get<bool>();
    return result;
}

Api::Changes Client::hasChanges(
    const Uuids::Uuid& sessionUuid
) const
{
    // Подготовка тела запроса
    Common::Json requestJson;
    requestJson["sessionUuid"] = Uuids::uuidToString(sessionUuid);

    // Формирование запроса
    const cpr::Response response = cpr::Post(
        cpr::Url { url("/api/v1/has_changes") },
        cpr::Body { requestJson.dump() },
        cpr::Header { { "Content-Type", "application/json" } }
    );

    // Разбор ответа
    Api::Changes result;
    const auto responseJson = Common::stringToJson(response.text);
    for (const auto& change : responseJson["changes"])
    {
        const auto elementType = Elements::ElementType(
            change["elementType"].get<unsigned>()
        );
        result[elementType] = change["ids"].get<std::set<unsigned>>();
    }
    return result;
}

Api::Result Client::login(
    const std::string& user,
    const std::string& password
) const
{
    // Подготовка тела запроса
    Common::Json requestJson;
    requestJson["user"] = user;
    requestJson["password"] = password;

    // Формирование запроса
    const cpr::Response response = cpr::Post(
        cpr::Url { url("/api/v1/login") },
        cpr::Body { requestJson.dump() },
        cpr::Header { { "Content-Type", "application/json" } }
    );

    // Разбор ответа
    Result result;
    const auto responseJson = Common::stringToJson(response.text);
    result.m_sessionUuid = Uuids::stringToUuid(responseJson["sessionUuid"].get<std::string>());
    result.m_isSuccess = bool(result.m_sessionUuid != Uuids::nilUuid());
    result.m_id = responseJson["id"].get<unsigned>();
    return result;
}

Api::Result Client::logout(
    const Uuids::Uuid& sessionUuid,
    const std::string& user
) const
{
    // Подготовка тела запроса
    Common::Json requestJson;
    requestJson["sessionUuid"] = Uuids::uuidToString(sessionUuid);
    requestJson["user"] = user;

    // Формирование запроса
    const cpr::Response response = cpr::Post(
        cpr::Url { url("/api/v1/logout") },
        cpr::Body { requestJson.dump() },
        cpr::Header { { "Content-Type", "application/json" } }
    );

    // Разбор ответа
    const auto responseJson = Common::stringToJson(response.text);
    Result result;
    result.m_isSuccess = responseJson["success"].get<bool>();
    return result;
}

std::string Client::url(const std::string& path) const
{
    return m_datenaroPath + path;
}

} // namespace Api
